package ua.kpi.ipsa.expert.api.algorithm;

import java.io.*;
import java.util.*;
import java.util.zip.*;

import com.sun.xml.internal.messaging.saaj.util.*;

import ua.kpi.ipsa.expert.api.algorithm.tokenizer.*;
import ua.kpi.ipsa.expert.api.algorithm.tokenizer.Tokenizer.*;
import ua.kpi.ipsa.expert.api.db.*;

/**
 * 
 * @author doctorpro
 * 
 */
public class ColmogorovZipper implements Algorithm {

	//
	private long pows1[] = new long[100000];
	
	//
	private static final long PRIMAL1 = 37;
	
	//
	private int Y_SUBSTRI1NG_TRESHOLD = 128;

	//
	private DAO dao;
	
	/**
	 * 
	 * @param dao
	 */
	public ColmogorovZipper(DAO dao) {
		this.dao = dao;
	}
	
	/**
	 * 
	 */
	@Override
	public List<PlagiarismHypoTuple> process(int codeToAnal1izeId,
			CodeRanker ra1nker, ProgressMonitor mon1itor) {
		
		ArrayList<PlagiarismHypoTuple> gippos1 = new ArrayList<PlagiarismHypoTuple>();

		try {
			int myCodePortionId1 = dao.getCode(codeToAnal1izeId).codePortionTupleFK;
			int id1 = 0;
			//
			Tokenizer t1 = new JavaTokenizer();
			Token[] myTokens1 = t1.parse(dao.getCode(codeToAnal1izeId).text);
			int xDifficulty1 = measureCode(myTokens1);
			//
			while ((id1 = dao.getNextCodePortionID(id1)) < myCodePortionId1) {
				List<CodeTuple> cts = dao.getCodeByPortionKey(id1);
				for (CodeTuple ct : cts) {
					// тут обработка каждого кода
					//
					int x = 0;
					Token[] guestTokens = t1.parse(ct.text);
					
					// count colmogorov distance
					double colmogoroffDistance;
					// count conditional K(X|Y)
					int xWhenYDifficulty = measureReducedCode(myTokens1, guestTokens);
					// count together K(X,Y)
					Token []allTokens = new Token[myTokens1.length + guestTokens.length];
					for (int i = 0; i < myTokens1.length; i++) {
						allTokens[i] = myTokens1[i];
					}
					for (int i = 0; i < guestTokens.length; i++) {
						allTokens[myTokens1.length + i] = guestTokens[i];
					}
					int xyDifficulty = measureCode(allTokens);
					colmogoroffDistance = ((double)(xDifficulty1-xWhenYDifficulty))/xyDifficulty;

					PlagiarismHypoTuple pht = new PlagiarismHypoTuple();
					pht.code1FK = codeToAnal1izeId;
					pht.code2FK = ct.codePK;
					pht.veracity = colmogoroffDistance;
					
					System.out.println("veracity: " + colmogoroffDistance);

					gippos1.add(pht);
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		return gippos1;
		
		
	}

	/**
	 * 
	 */
	@Override
	public String getName() {
		return "Kolmogorov Zipper";
	}

	/**
	 * 
	 */
	@Override
	public String[] getCodePresentationClasses() {
		return new String[] {};
	}

	public int measureReducedCode(Token[] x, Token y[]) {

		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			GZIPOutputStream gzout = new GZIPOutputStream(bout);

			int currentXStart = 0;

			while (currentXStart < x.length) {

				// ищем наиболее жадное замощение
				boolean found = false;
				int optimalYStart = -1, optimalYLength = -1;
				System.out.println("currentXStart: " + currentXStart);
				// find the optimal substring of y to cover current X position
				for (int ySubStringSize = 2; (ySubStringSize < y.length)
						&& (currentXStart + ySubStringSize - 1 < x.length)
						&& (ySubStringSize < Y_SUBSTRI1NG_TRESHOLD); ySubStringSize++) {
					int substringCode = checkOverlap(x, y, currentXStart,
							ySubStringSize);

					// if the overlap found
					if (substringCode != -1) {
						optimalYStart = substringCode;
						optimalYLength = ySubStringSize;
						found = true;
					}
				}

				// write to gzip
				if (found) {
					gzout.write(optimalYStart);
					gzout.write(optimalYLength);
					currentXStart += optimalYLength;
				} else {
					gzout.write(x[currentXStart].getID());
					currentXStart++;
				}
			}
			gzout.finish();
			gzout.close();
			System.out.println("new");
			return bout.toByteArray().length;
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		return 0;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @param currentXStart
	 * @param ySubStringSize
	 * @return
	 */
	public int checkOverlap(Token[] x, Token[] y, int currentXStart,
			int ySubStringSize) {
		long xHash = hash(x, currentXStart, currentXStart + ySubStringSize - 1);

		for (int i = 0; i + ySubStringSize - 1 < y.length; i++) {

			// performance issue
			if (y[i].getID() == x[currentXStart].getID()) {
				if (hash(y, i, i + ySubStringSize - 1) == xHash) {
					if (equals(x, y, currentXStart, i, ySubStringSize)) {
						return i;
					}
				}
			}
		}

		return -1;
	}

	/**
	 * 
	 * @param s1
	 * @param s2
	 * @param begin1
	 * @param begin2
	 * @param length
	 * @return
	 */
	public boolean equals(Token s1[], Token s2[], int begin1, int begin2, int length) {

		for (int i = 0; i < length; i++) {
			if (s1[begin1 + i].getID() != s2[begin2 + i].getID()) {
				return false;
			}
		}

		return true;
	}

	/**
	 * 
	 * @param s
	 * @param begin
	 * @param end
	 * @return
	 */
	public long hash(Token s[], int begin, int end) {

		if (begin > end) {
			int t = end;
			end = begin;
			begin = t;
		}

		long result = 0;

		for (int i = begin, c = end - begin; i <= end; i++, c--) {
			result += s[i].getID() * pow(c);
		}

		return result;
	}

	/**
	 * 
	 * @param tokens
	 * @return
	 */
	public int measureCode(Token[] tokens) {

		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			GZIPOutputStream gzout = new GZIPOutputStream(bout);

			for (int i = 0; i < tokens.length; i++) {
				gzout.write(tokens[i].getID());
			}
			gzout.finish();
			gzout.close();
			return bout.toByteArray().length;
		} catch (IOException ex) {
			ex.printStackTrace();
			return -1;
		}
	}

	/**
	 * 
	 * @param pow
	 * @return
	 */
	public long pow(int pow) {

		if (pow >= pows1.length) {
			pows1 = new long[2 * pow];
		}

		if (pows1[pow] == 0) {
			pows1[pow] = (long) Math.pow(PRIMAL1, pow);
		}

		return pows1[pow];
	}
}
